package com.apes.fico.finance.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.apes.fico.engine.repository.VoucherTemplateRepository;
import com.apes.fico.finance.model.gl.CarryOverPL;
import com.apes.fico.finance.model.gl.ChartAmount;
import com.apes.fico.finance.model.gl.ChartAux;
import com.apes.fico.finance.model.gl.GLVoucher;
import com.apes.fico.finance.respository.*;
import com.apes.fico.masterdata.gl.model.AccountingPeriod;
import com.apes.fico.masterdata.gl.model.AccountingPeriodItem;
import com.apes.fico.masterdata.gl.respository.AccountingOperatingChartAuxiliaryRepository;
import com.apes.fico.masterdata.gl.respository.AccountingPeriodItemRepository;
import com.apes.fico.masterdata.gl.respository.AccountingPeriodRepository;
import com.apes.framework.api.DomainService;
import com.apes.framework.api.SpringManager;
import com.apes.framework.plugin.esb.api.SimpleRequest;
import com.apes.framework.plugin.outbox.OutboxEvent;
import com.apes.framework.plugin.outbox.OutboxEventService;
import com.apes.framework.plugin.outbox.OutboxPayloadWrapper;
import com.apes.framework.plugin.outbox.OutboxSubscriberService;
import com.apes.framework.plugin.outbox.annotation.OutboxSubscriber;
import com.apes.framework.util.MapUtil;
import com.apes.framework.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @ClassName CarryOverPLService
 * @Description 损益结转
 * @Author pengyb
 * @Date 2019/1/30 11:44
 * @Version 1.0
 **/
@Service("carryOverPLService")
public class CarryOverPLService extends DomainService {

    @Autowired
    private CarryOverPLRepository carryOverPLRepository;
    @Autowired
    private VoucherTemplateRepository voucherTemplateRepository;
    @Autowired
    private GLVoucherRepository glVoucherRepository;
    @Autowired
    private AccountingPeriodRepository accountingPeriodRepository;
    @Autowired
    private ChartAmoutRepository chartAmoutRepository;
    @Autowired
    private ChartAuxRepository chartAuxRepository;
    @Autowired
    private ChartService chartService;
    @Autowired
    private GLVoucherItemRepository glVoucherItemRepository;
    @Autowired
    private AccountingOperatingChartAuxiliaryRepository accOpChartAuxRepository;

    public static final String GLVOUCHAR_CREATE = "finance.GLVoucher.create";
    public static final String GLVOUCHAR_DELETE = "finance.GLVoucher.delete";
    public static final String GLVOUCHAR_OVERAPPROVE = "finance.GLVoucher.overApprove";
    public static final String CHART_AMOUNT_MONTHLY = "finance.chartAmount.monthly";
    public static final String CHART_AUX_MONTHLY = "finance.chartAux.monthly";

    /**
     * 结转损溢入口
     */
    public CarryOverPL create(SimpleRequest request) {
        CarryOverPL carryOverPL = request.getO(CarryOverPL.class);
        carryOverPL.setPeriod(carryOverPL.getPeriodId().getPeriod_name());
        carryOverPL.setType(0);
        carryOverPL.setState("create");
        carryOverPL.setCreateDate(new Date());
        //结转检查
        checkGLVoucher(carryOverPL, carryOverPL.getType());
        carryOverPL = carryOverPLRepository.save(carryOverPL);
        //检查凭证状态
        if(glVoucherRepository.findByCompanyAndPeriod(carryOverPL.getCompany().getId(),carryOverPL.getPeriod()) != 0) throw new RuntimeException("有未审核凭证，无法结转");
        //调用结转损溢存储过程 参数：1.公司编码 1001 2.会计期间 202206 3.创建人编码
        EntityManager entityManager = SpringManager.getBean(EntityManager.class);
        Query query = entityManager.createNativeQuery("BEGIN EXEC_GL_CARRY_OVER_PROFIT_LOSS (:companyId,:yearMonth,:creatorId); END;")
                .setParameter("companyId",carryOverPL.getCompany().getId())
                .setParameter("yearMonth",carryOverPL.getPeriodId().getYearMonth())
                .setParameter("creatorId",carryOverPL.getCreator());
        int i = 0;
        try {
            i = query.executeUpdate();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        if (i <= 0) {
            throw new RuntimeException("执行结转损溢失败");
        }
        return carryOverPL;
    }

    /**
     * 取消结转损溢
     * @param request
     * @return
     */
    public CarryOverPL cancel(SimpleRequest request) {
        CarryOverPL carryOverPL = request.getO(CarryOverPL.class);
        carryOverPL.setPeriod(carryOverPL.getPeriodId().getPeriod_name());
        carryOverPL.setType(1);
        carryOverPL.setState("cancel");
        carryOverPL.setCreateDate(new Date());

        //结转检查
        checkGLVoucher(carryOverPL, carryOverPL.getType());
        carryOverPL = carryOverPLRepository.save(carryOverPL);

        cancelCarryOverPL(carryOverPL);

        return carryOverPL;
    }

    /**
     * 月结
     * @param request
     * @return
     */
    public CarryOverPL monthly(SimpleRequest request) {
        CarryOverPL carryOverPL = request.getO(CarryOverPL.class);
        int outboxEvent = carryOverPLRepository.findOutboxEvent(carryOverPL.getCompany().getId(), carryOverPL.getPeriodId().getYearMonth());
        if(outboxEvent != 0) throw new RuntimeException("当前还有"+outboxEvent+"条发生额数据在处理，请稍后再结账");
        this.invoke("fa.card.findByDate",MapUtil.mapper("companyId",carryOverPL.getCompany().getId(),"periodDate",carryOverPL.getPeriodId().getFromDate()));
        carryOverPL.setPeriod(carryOverPL.getPeriodId().getPeriod_name());
        carryOverPL.setUpdateDate(new Date());
        carryOverPL.setMonthlyFlag(true);
        carryOverPL.setState("month");
        carryOverPL = carryOverPLRepository.saveAndFlush(carryOverPL);

        AccountingPeriodItem accountingPeriodItem = SpringManager.getBean(AccountingPeriodItemRepository.class).findByPeriodAndCompany(carryOverPL.getPeriod(), carryOverPL.getCompany().getId());
        if (accountingPeriodItem.isMonthlyFlag()) throw new RuntimeException("重复月结！");
        accountingPeriodItem.setMonthlyFlag(true);
        SpringManager.getBean(AccountingPeriodItemRepository.class).saveAndFlush(accountingPeriodItem);

        //月结处理下月科目发生额期余额
        syncMonthly(carryOverPL);
        return carryOverPL;
    }

    /**
     * 取消月结
     * @param request
     * @return
     */
    public CarryOverPL cancelMonthly(SimpleRequest request) {
        CarryOverPL carryOverPL = request.getO(CarryOverPL.class);
        carryOverPL.setPeriod(carryOverPL.getPeriodId().getPeriod_name());
        carryOverPL.setUpdateDate(new Date());
        carryOverPL.setMonthlyFlag(false);
        carryOverPL.setState("create");
        carryOverPL = carryOverPLRepository.saveAndFlush(carryOverPL);

        AccountingPeriodItem accountingPeriodItem = SpringManager.getBean(AccountingPeriodItemRepository.class).findByPeriodAndCompany(carryOverPL.getPeriod(), carryOverPL.getCompany().getId());
        if (!accountingPeriodItem.isMonthlyFlag()) throw new RuntimeException("重复取消月结！");

        List<AccountingPeriodItem> accountingPeriodItemList = SpringManager.getBean(AccountingPeriodItemRepository.class).findByCompanyAndYearMonth(carryOverPL.getCompany().getId(),carryOverPL.getPeriodId().getYearMonth());
        if (accountingPeriodItemList.size()>0) throw new RuntimeException("后续月份已经结账请先取消结账！");

        accountingPeriodItem.setMonthlyFlag(false);
        SpringManager.getBean(AccountingPeriodItemRepository.class).saveAndFlush(accountingPeriodItem);

        syncCancelMonthly(carryOverPL);
        return carryOverPL;
    }

    /**
     * 检测凭证状态
     */
    private void checkVoucherState(CarryOverPL carryOverPL){
        List<GLVoucher> glVoucherList =  glVoucherRepository.findByCompany_IdAndPeriod(carryOverPL.getCompany().getId(),carryOverPL.getPeriod());
        if (glVoucherList.size() == 0) throw new RuntimeException("该公司" + carryOverPL.getPeriod() + "未发现需结转帐，请检查！");

        glVoucherList.forEach(item -> {
            if (!item.getState().equals("approve"))
                throw new RuntimeException("凭证" + item.getId() + "未审核！无法结转");
        });
    }

    /**
     * 取消损益结转
     */
    private void cancelCarryOverPL(CarryOverPL carryOverPL) {
        AccountingPeriodItem accPeriodItem = SpringManager.getBean(AccountingPeriodItemRepository.class).findByPeriodAndCompany(carryOverPL.getPeriod(), carryOverPL.getCompany().getId());
        accPeriodItem.setCarryOverPLFlag(false);
        SpringManager.getBean(AccountingPeriodItemRepository.class).saveAndFlush(accPeriodItem);

        GLVoucher voucher = glVoucherRepository.findByPeriodAndCompany_IdAndVoucherTemplate_Description(carryOverPL.getPeriod(), carryOverPL.getCompany().getId(), "损益结转");
        if (voucher == null) return;
        voucher.setCancelCarryOverPL(true);
        this.invoke(GLVOUCHAR_OVERAPPROVE, MapUtil.mapped(voucher));
        this.invoke(GLVOUCHAR_DELETE, MapUtil.mapped(voucher));
    }


    /**
     * 结转检查
     * @param carryOverPL
     */
    private void checkGLVoucher(CarryOverPL carryOverPL, int type) {
        if (type == 0) {
            GLVoucher voucher = glVoucherRepository.findByPeriodAndCompany_IdAndVoucherTemplate_Description(carryOverPL.getPeriod(), carryOverPL.getCompany().getId(), "损益结转");
            if (voucher != null) throw new RuntimeException("重复结转！凭证内码：" + voucher.getId());
        }
        AccountingPeriod accountingPeriod = accountingPeriodRepository.findByCompany_IdAndYear(carryOverPL.getCompany().getId(), carryOverPL.getYear());
        AccountingPeriodItem periodItem = accountingPeriod.getAccountingPeriodItems().stream().filter(accountingPeriodItem -> accountingPeriodItem.getPeriod_name().equals(carryOverPL.getPeriod())).collect(Collectors.toList()).get(0);
        if (type == 1 && periodItem.isMonthlyFlag()) throw new RuntimeException("已经月结，请先取消月结");
        List<AccountingPeriodItem> periodItems;
        if (type == 0) {
            periodItems = accountingPeriod.getAccountingPeriodItems().stream().filter(item -> item.getMonth() < periodItem.getMonth()).collect(Collectors.toList());
        } else {
            periodItems = accountingPeriod.getAccountingPeriodItems().stream().filter(item -> item.getMonth() > periodItem.getMonth()).collect(Collectors.toList());
        }

        periodItems.forEach(item -> {
            if (!item.isMonthlyFlag() && type == 0) throw new RuntimeException(item.getPeriod_name() + "未结转！请先结转");
            if (!item.isMonthlyFlag() && type == 1 && item.isCarryOverPLFlag()) throw new RuntimeException(item.getPeriod_name() + "未反结转！请先结转");
        });
    }

    /**
     * 月结操作
     *
     * @param carryOverPL
     */
    public void syncMonthly(CarryOverPL carryOverPL) {
        GLVoucher glVoucher = glVoucherRepository.findByPeriodAndCompany_IdAndVoucherTemplate_VoucherTemplateGroup_Id(carryOverPL.getPeriod(), carryOverPL.getCompany().getId(), "998");
        int yearMonth;
        if(Objects.isNull(glVoucher)){
            yearMonth = carryOverPL.getPeriodId().getYearMonth();
        }else {
            yearMonth = glVoucher.getYearMonth();
        }
        //分页查询
        Page<Map> chartAuxList = chartAuxRepository.findAllIdByPeriodAndCompany_Id(yearMonth, carryOverPL.getCompany().getId(), PageRequest.of(0,200));
        Page<Map> chartAmountList = chartAmoutRepository.findAllIdByPeriodAndCompany_Id(yearMonth, carryOverPL.getCompany().getId(), PageRequest.of(0,200));

        //推送财务月结更新科目发生额数据信息
        sendRequest(chartAuxList.getContent());
        sendRequest(chartAmountList.getContent());

        //循环每一页推送数据
        for (int i = chartAuxList.getNumber() + 1; i < chartAuxList.getTotalPages(); i++) {
            Page<Map> list = chartAuxRepository.findAllIdByPeriodAndCompany_Id(yearMonth,
                    carryOverPL.getCompany().getId(), PageRequest.of(i,200));

            //推送财务月结更新科目发生额数据信息
            sendRequest(list.getContent());
        }

        //循环每一页推送数据
        for (int i = chartAmountList.getNumber() + 1; i < chartAmountList.getTotalPages(); i++) {
            Page<Map> list = chartAmoutRepository.findAllIdByPeriodAndCompany_Id(yearMonth,
                    carryOverPL.getCompany().getId(), PageRequest.of(i,200));

            //推送财务月结更新科目发生额数据信息
            sendRequest(list.getContent());
        }
    }

    /**
     * 取消月结
     * @param carryOverPL
     */
    public void syncCancelMonthly(CarryOverPL carryOverPL) {
        String companyId = carryOverPL.getCompany().getId();

        //根据会计期间档案取对应会计期间
        AccountingPeriod accountingPeriod = accountingPeriodRepository.findByCompany_IdAndYear(companyId, carryOverPL.getYear());
        AccountingPeriodItem accountingPeriodItem = accountingPeriod.getAccountingPeriodItems().stream()
                .filter(accountingPeriodItems -> accountingPeriodItems.getPeriod_name().equals(carryOverPL.getPeriod()))
                .collect(Collectors.toList()).get(0);

        //如果12月 取消月结 将次年 期初余额清0 一般都是清下月的期初余额
        int period;
        if (accountingPeriodItem.getYearMonth() % 100 == 12) {
            period = accountingPeriodItem.getYearMonth() + 89;
        } else {
            period = accountingPeriodItem.getYearMonth() + 1;
        }

        //科目发生额和科目发生额辅助核算期初余额清0
        chartAuxRepository.updateOpenBalanceByPeriodAndCompany_Id(period,companyId);
        chartAmoutRepository.updateOpenBalanceByPeriodAndCompany_Id(period,companyId);

        if (accountingPeriodItem.getYearMonth() % 100 == 12){
            chartService.cancelWriteInit(period, companyId);
        }
    }

    /**
     * 发送财务结转月结发生额数据到事务发件箱
     * @param content
     */
    public void sendRequest(List<Map> content){
        //推送财务月结更新科目发生额数据信息
        OutboxPayloadWrapper wrapper = new OutboxPayloadWrapper();
        wrapper.setSubscriber("local:CarryOver.syncMonthlyChart");
        wrapper.setPayload(JSON.toJSONString(content));
        wrapper.setAggregateId("CarryOver.syncMonthlyChart " + System.currentTimeMillis());
        SpringManager.getBean(OutboxEventService.class).sendSaveEvent(wrapper);
    }

    @OutboxSubscriber(id="local:CarryOver.syncMonthlyChart", name="财务结账执行月结操作更新科目发生额")
    public void syncMonthlyChart(OutboxPayloadWrapper outboxPayloadWrapper){
        String payload = outboxPayloadWrapper.getPayload();
        JSONArray.parseArray(payload).forEach(item -> {
            JSONObject json = (JSONObject) item;
            String id = json.get("id").toString();
            String companyId = json.get("company_id").toString();
            String period = json.get("period").toString();
            String type = json.get("type").toString();
            //推送消费信息
            SpringManager.getBean(OutboxSubscriberService.class)
                    .commonSendRequest("local:CarryOver.syncMonthlyChartByType",
                            "CarryOver.syncMonthlyChartByType?" + companyId + "&" + period + "&" + id  , new JSONObject(MapUtil.mapper(
                                    "id", id , "type" , type
                            )));
        });
    }

    @OutboxSubscriber(id="local:CarryOver.syncMonthlyChartByType", name="财务执行月结操作更新科目发生额数据")
    public void syncMonthlyChartByType(OutboxPayloadWrapper outboxPayloadWrapper){
        JSONObject object = JSONObject.parseObject(outboxPayloadWrapper.getPayload());
        String id = object.getString("id");
        String type = object.getString("type"); // 0:为无辅助核算 1：为有辅助核算
        int glVoucher = 1;
        if(Objects.equals("0",type)){
            ChartAmount chartAmount = chartAmoutRepository.findOne(id);
            //期初余额和本期借贷方发生额均为0且不存在财务凭证明细数据，不写入下一个月的发生额表内
            if(chartAmount.getCredit() == 0 && chartAmount.getDebit() == 0 && chartAmount.getOpeningBalance() == 0 && chartAmount.getUnCredit() == 0 && chartAmount.getUnDebit() == 0){
                glVoucher = glVoucherRepository.findByOperatingChart(chartAmount.getCompany().getId(), chartAmount.getPeriod(), chartAmount.getAccountingOperatingChart().getId());
            }
            if(glVoucher == 0) return;
            this.invoke(CHART_AMOUNT_MONTHLY, MapUtil.mapped(chartAmount));
        }else if (Objects.equals("1",type)){
            ChartAux chartAux = chartAuxRepository.findOne(id);
            //期初余额和本期借贷方发生额均为0且不存在财务凭证明细数据，不写入下一个月的发生额表内
            if(chartAux.getCredit() == 0 && chartAux.getDebit() == 0 && chartAux.getOpeningBalance() == 0 && chartAux.getUnCredit() == 0 && chartAux.getUnDebit() == 0){
                glVoucher = glVoucherRepository.findByOperatingChart(chartAux.getCompany().getId(), chartAux.getPeriod(), chartAux.getAccountingOperatingChart().getId());
            }
            if(glVoucher == 0) return;
            this.invoke(CHART_AUX_MONTHLY, MapUtil.mapped(chartAux));
        }
    }

}
